/* eslint-disable no-underscore-dangle */
// noinspection JSUnusedAssignment

"use strict";

const path = require("path");
const VirtualStats = require("./virtual-stats.js");

class VirtualModulePlugin {
	constructor(options) {
		this.options = options;
	}

	static populateFilesystem(options) {
		const fs = options.fs;
		const modulePath = options.modulePath;
		const contents = options.contents;
		const mapIsAvailable = typeof Map !== "undefined";
		const wpversion = Number(options.wpversion);
		let statStorage, readFileStorage;
		if (wpversion === 5) {
			statStorage = fs._statBackend._data;
			readFileStorage = fs._readFileBackend._data;
		} else {
			statStorage = fs._statStorage.data;
			readFileStorage = fs._readFileStorage.data;
		}
		const statStorageIsMap = mapIsAvailable && statStorage instanceof Map;
		const readFileStorageIsMap = mapIsAvailable && readFileStorage instanceof Map;

		if (readFileStorageIsMap) { // enhanced-resolve@3.4.0 or greater
			if (readFileStorage.has(modulePath)) {
				return;
			}
		} else if (readFileStorage[modulePath]) { // enhanced-resolve@3.3.0 or lower
			return;
		}
		const stats = VirtualModulePlugin.createStats(options);
		if (statStorageIsMap) { // enhanced-resolve@3.4.0 or greater
			statStorage.set(modulePath, [null, stats]);
		} else { // enhanced-resolve@3.3.0 or lower
			statStorage[modulePath] = [null, stats];
		}
		if (readFileStorageIsMap) { // enhanced-resolve@3.4.0 or greater
			readFileStorage.set(modulePath, [null, contents]);
		} else { // enhanced-resolve@3.3.0 or lower
			readFileStorage[modulePath] = [null, contents];
		}
	}

	static statsDate(inputDate) {
		if (!inputDate) {
			inputDate = new Date();
		}
		return inputDate.toString();
	}

	static createStats(options) {
		if (!options) {
			options = {};
		}
		if (!options.ctime) {
			options.ctime = VirtualModulePlugin.statsDate();
		}
		if (!options.mtime) {
			options.mtime = VirtualModulePlugin.statsDate();
		}
		if (!options.size) {
			options.size = 0;
		}
		if (!options.size && options.contents) {
			options.size = options.contents.length;
		}
		return new VirtualStats({
			dev: 8675309,
			nlink: 1,
			uid: 501,
			gid: 20,
			rdev: 0,
			blksize: 4096,
			ino: 44700000,
			mode: 33188,
			size: options.size,
			atime: options.mtime,
			mtime: options.mtime,
			ctime: options.ctime,
			birthtime: options.ctime
		});
	}

	apply(compiler) {
		const moduleName = this.options.moduleName;
		const ctime = VirtualModulePlugin.statsDate();
		let modulePath = this.options.path;

		let contents;
		if (typeof this.options.contents === "string") {
			contents = this.options.contents;
		}
		if (typeof this.options.contents === "object") {
			if (typeof this.options.contents.then !== "function") {
				contents = JSON.stringify(this.options.contents);
			}
		}
		if (typeof this.options.contents === "function") {
			contents = this.options.contents();
		}
		if (typeof contents === "string") {
			contents = Promise.resolve(contents);
		}

		function resolverPlugin(request, cb) {
			// populate the file system cache with the virtual module
			const fs = (this && this.fileSystem) || compiler.inputFileSystem;
			const join = (this && this.join) || path.join;

			// webpack 1.x compatibility
			if (typeof request === "string") {
				request = cb;
				cb = null;
			}

			if (!modulePath) {
				modulePath = join(compiler.context, moduleName);
			}

			const resolve = (data) => {
				VirtualModulePlugin.populateFilesystem({fs, modulePath, contents: data, ctime});
			};

			const resolved = contents.then(resolve);
			if (!cb) {
				return;
			}

			resolved.then(() => cb());
		}

		const waitForResolvers = !compiler.resolvers.normal;

		function addPlugin() {
			const useModuleFactory = !compiler.resolvers.normal.plugin;
			if (useModuleFactory) {
				if (compiler.hooks) {
					compiler.hooks.normalModuleFactory.tap("VirtualModulePlugin", (nmf) => {
						nmf.hooks.beforeResolve.tap("VirtualModulePlugin", resolverPlugin);
					});
				} else {
					compiler.plugin("normal-module-factory", (nmf) => {
						nmf.plugin("before-resolve", resolverPlugin);
					});
				}
			} else {
				compiler.resolvers.normal.plugin("before-resolve", resolverPlugin);
			}
		}

		if (waitForResolvers) {
			compiler.plugin("after-resolvers", addPlugin);
		} else {
			addPlugin();
		}
	}
}

module.exports = VirtualModulePlugin;
